﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace nr.Tombola.Net
{
    /// <summary>
    /// Il client di gioco.
    /// </summary>
    /// <remarks>Gestisce la comunicazione con un server di gioco e smista i messaggi attraverso una serie di eventi.</remarks>
    public class GameClient
    {
        /// <summary>
        /// Sollevato quando viene inviato lo stato di una partita in corso.
        /// </summary>
        public event GameStatusEventHandler GameStatus;

        /// <summary>
        /// Sollevato quando è stata instaurata una connessione con il server.
        /// </summary>
        public event ClientConnectedEventHandler Connected;

        /// <summary>
        /// Sollevato quando è chiusa una connessione con il server.
        /// </summary>
        public event ClientEventHandler Disconnected;

        /// <summary>
        /// Sollevato quando è stato ricevuto un messaggio da parte del server.
        /// </summary>
        public event MessageReceivedEventHandler MessageReceived;

        /// <summary>
        /// Sollevato quando il server comunica l'estrazione di un numero.
        /// </summary>
        public event NumberExtractEventHandler NumberExtract;

        /// <summary>
        /// Sollevato quando incomincia una nuova partita.
        /// </summary>
        public event StartGameEventHandler StartGame;

        /// <summary>
        /// Sollevato quando termina una partita.
        /// </summary>
        public event EndGameEventHandler EndGame;

        /// <summary>
        /// Sollevato quando il server assegna un ID al client.
        /// </summary>
        public event ClientIdAssignedEventHandler ClientIDAssigned;

        /// <summary>
        /// Indirizzo sul quale il server è in ascolto.
        /// </summary>
        public System.Net.IPAddress ServerAddress { get; set; }

        /// <summary>
        /// Porta di ascolto del server.
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// Indica se la connessione è attiva.
        /// </summary>
        public bool Running
        {
            get { return Client.Connected; }
            private set
            {
                if (value)
                    try
                    {
                        Client.Connect(ServerAddress, Port);
                    }
                    catch (Exception ex)
                    {
                        throw new CommunicationException("Cannot connect with the server.", ex);
                    }
                else
                {
                    Client.Close();
                    OnDisconnected(new ClientEventArgs());
                }
            }
        }

        private System.Threading.Thread ConnectionThread { get; set; }

        private System.Net.Sockets.TcpClient Client { get; set; }

        /// <summary>
        /// Costruttore di default.
        /// </summary>
        /// <remarks>Assume che il server sia in ascolto su localhost sulla porta 9090.</remarks>
        public GameClient() : this("127.0.0.1", 9090) { }

        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="serverAddress">Indirizzo di ascolto del server.</param>
        /// <param name="port">Porta di ascolto del server.</param>
        public GameClient(string serverAddress, int port) : this(System.Net.IPAddress.Parse(serverAddress), port) { }

        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="serverAddress">Indirizzo di ascolto del server.</param>
        /// <param name="port">Porta di ascolto del server.</param>
        public GameClient(System.Net.IPAddress serverAddress, int port)
        {
            ServerAddress = serverAddress;
            Port = port;
            Client = new System.Net.Sockets.TcpClient();
        }

        /// <summary>
        /// Apre la connessione con il server.
        /// </summary>
        public void Connect()
        {
            Running = true;
            OnConnected(new ClientConnectedEventArgs(ServerAddress.ToString(), Port));
            ConnectionThread = new System.Threading.Thread(HandleConnection);
            ConnectionThread.Start();
        }

        /// <summary>
        /// Gestisce la comunicazione con il server.
        /// </summary>
        private void HandleConnection()
        {
            System.IO.StreamReader sr = new System.IO.StreamReader(Client.GetStream(), Encoding.ASCII);
            while (Running)
                ParseCommand(sr.ReadLine());
        }

        /// <summary>
        /// Analizza un messaggio del server e svolge le operazioni in esso indicate.
        /// </summary>
        /// <param name="message">Messaggio del server.</param>
        private void ParseCommand(string message)
        {
            string[] parts = message.Split(':');
            int code;
            if (int.TryParse(parts[0], out code))
            {
                try
                {

#if DEBUG
                    OnMessageReceived(new MessageReceivedEventArgs(message));
#endif

                    switch (code)
                    {
                        case 001:
                            OnStartGame(new StartGameEventArgs());
                            break;
                        case 010:
                            OnNumberExtract(new NumberExtractEventArgs(int.Parse(parts[2])));
                            break;
                        case 100:
                            OnEndGame(new EndGameEventArgs());
                            break;
                        case 110:
                            OnClientIDAssigned(new ClientIdAssignedEventArgs(new Guid(parts[2])));
                            break;
                        case 111:
                            Running = false;
                            break;
                        case 1000:
                            OnGameStatus(new GameStatusEventArgs(message.Substring(10)));
                            break;
                        default:
                            OnMessageReceived(new MessageReceivedEventArgs(message));
                            break;
                    }
                }
                catch (Exception ex)
                {
                    throw new CommunicationException("Syntax error in server message (see InnerException for more informations).", ex);
                }
            }
            else
                throw new CommunicationException("Syntax error in server message (see Info property for more informations).") { Info = message };
        }

                /// <summary>
        /// Chiude la connessione con il server.
        /// </summary>
        public void Disconnect()
        {
            Running = false;
        }

        /// <summary>
        /// Solleva l'evento MessageReceived.
        /// </summary>
        /// <param name="e">Argomenti dell'evento.</param>
        protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
        {
            if (MessageReceived != null) MessageReceived(this, e);
        }

        /// <summary>
        /// Solleva l'evento ServerConnected.
        /// </summary>
        /// <param name="e">Argomenti dell'evento.</param>
        protected virtual void OnConnected(ClientConnectedEventArgs e)
        {
            if (Connected != null) Connected(this, e);
        }

        /// <summary>
        /// Solleva l'evento NumberExtract.
        /// </summary>
        protected virtual void OnNumberExtract(NumberExtractEventArgs e)
        {
            if (NumberExtract != null) NumberExtract(this, e);
        }

        /// <summary>
        /// Solleva l'evento StartGame.
        /// </summary>
        protected virtual void OnStartGame(StartGameEventArgs e)
        {
            if (StartGame != null) StartGame(this, e);
        }

        /// <summary>
        /// Solleva l'evento EndGame.
        /// </summary>
        protected virtual void OnEndGame(EndGameEventArgs e)
        {
            if (EndGame != null) EndGame(this, e);
        }

        /// <summary>
        /// Solleva l'evento ClientIDAssigned.
        /// </summary>
        protected void OnClientIDAssigned(ClientIdAssignedEventArgs e)
        {
            if (ClientIDAssigned != null) ClientIDAssigned(this, e);
        }

        /// <summary>
        /// Solleva l'evento ServerDisconnected.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDisconnected(ClientEventArgs e)
        {
            if (Disconnected != null) Disconnected(this, e);
        }

        /// <summary>
        /// Sollega l'evento GameStatus.
        /// </summary>
        /// <param name="e"></param>
        private void OnGameStatus(GameStatusEventArgs e)
        {
            if (GameStatus != null) GameStatus(this, e);
        }
    }
}
